package org.ricks.ioc.utils;

import java.beans.Transient;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ReflectUtils {

    public static <T> T newInstance(Class<T> clazz) {
        try {
            return newInstance(clazz.getDeclaredConstructor());
        } catch (NoSuchMethodException e) {
            throw new RunException("[{}]无法被实例化", clazz);
        }
    }

    public static <T> T newInstance(Constructor<T> constructor) {
        try {
            return constructor.newInstance();
        } catch (Exception e) {
            throw new RunException("[{}]无法被实例化", constructor);
        }
    }

    /**
     * Invoke the specified {@link Method} against the supplied target object with the
     * supplied arguments. The target object can be {@code null} when invoking a
     * static {@link Method}.
     *
     * @param target the target object to invoke the method on
     * @param method the method to invoke
     * @param args   the invocation arguments (may be {@code null})
     * @return the invocation result, if any
     */
    public static Object invokeMethod(Object target, Method method, Object... args) {
        try {
            method.setAccessible(true);
            return method.invoke(target, args);
        } catch (Exception e) {
            throw new IllegalStateException("Unexpected reflection exception - " + e.getClass().getName() + ": " + e.getMessage());
        }
    }


    /**
     * 从一个Class中获得具有指定注解的Method，只获取子类的Method，不获取父类的Method
     *
     * @param clazz      指定的Class
     * @param annotation 指定注解的Class
     * @return 数组，可能长度为0
     */
    public static Method[] getMethodsByAnnoInPOJOClass(Class<?> clazz, Class<? extends Annotation> annotation) {
        var list = new ArrayList<Method>();
        var methods = clazz.getDeclaredMethods();
        for (var method : methods) {
            if (method.isAnnotationPresent(annotation)) {
                list.add(method);
            }
        }
        return ArrayUtils.listToArray(list, Method.class);
    }

    public static boolean isPojoClass(Class<?> clazz) {
        return clazz.getSuperclass().equals(Object.class) || clazz.isRecord();
    }

    /**
     * 从一个指定的POJO的Class中获得具有指定注解的Field，只获取子类的Field，不获取父类的Field
     *
     * @param clazz      指定的Class
     * @param annotation 指定注解的Class
     * @return 数组，可能长度为0
     */
    public static Field[] getFieldsByAnnoInPOJOClass(Class<?> clazz, Class<? extends Annotation> annotation) {
        var list = new ArrayList<Field>();
        var fields = clazz.getDeclaredFields();
        for (var field : fields) {
            if (field.isAnnotationPresent(annotation)) {
                list.add(field);
            }
        }
        return ArrayUtils.listToArray(list, Field.class);
    }

    public static Method getMethodByNameInPOJOClass(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(StringUtils.format("[class:{}] has no [method:{}] exception", clazz, methodName), e);
        }
    }

    public static Field getFieldByNameInPOJOClass(Class<?> clazz, String fieldName) {
        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            throw new IllegalStateException(StringUtils.format("[class:{}] has no [field:{}] exception", clazz, fieldName), e);
        }
    }

    public static void setField(Field field, Object target, Object value) {
        try {
            field.set(target, value);
        } catch (Exception e) {
            throw new IllegalStateException("Unexpected reflection exception - " + e.getClass().getName() + ": " + e.getMessage());
        }
    }

    public static Method[] getAllMethods(Class<?> clazz) {
        AssertionUtils.notNull(clazz, "Class must not be null");
        var list = new ArrayList<Method>();
        var superClazz = clazz;
        while (superClazz != null) {
            var methods = superClazz.getDeclaredMethods();
            Collections.addAll(list, methods);
            superClazz = superClazz.getSuperclass();
        }
        return ArrayUtils.listToArray(list, Method.class);
    }

    public static void assertIsPojoClass(Class<?> clazz) {
        if (!isPojoClass(clazz)) {
            throw new RunException("[class:{}]不是简单的javabean（POJO类不能继承别的类，但是可以继承其它接口）", clazz.getName());
        }
    }

    public static Constructor<?> publicEmptyConstructor(Class<?> clazz) {
        Constructor<?> constructor;

        try {
            constructor = clazz.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            throw new UnknownException(e, "[class:{}] should have exactly one public zero-argument constructor", clazz.getCanonicalName());
        }

        if (!Modifier.isPublic(constructor.getModifiers())) {
            throw new UnknownException("[class:{}] should have exactly one public zero-argument constructor", clazz.getCanonicalName());
        }

        return constructor;
    }

    public static Field[] getFieldsByAnnoNameInPOJOClass(Class<?> clazz, String annotationName) {
        var list = new ArrayList<Field>();
        var fields = clazz.getDeclaredFields();
        for (var field : fields) {
            var annotations = field.getAnnotations();
            if (ArrayUtils.isEmpty(annotations)) {
                continue;
            }

            for (var annotation : annotations) {
                if (annotation.annotationType().getName().equals(annotationName)) {
                    list.add(field);
                }
            }
        }
        return ArrayUtils.listToArray(list, Field.class);
    }

    // 获取class中的普通field属性字段
    public static List<Field> notStaticAndTransientFields(Class<?> clazz) {
        return Arrays.stream(clazz.getDeclaredFields())
                .filter(it -> !Modifier.isStatic(it.getModifiers()))
                .filter(it -> !Modifier.isTransient(it.getModifiers()))
                .filter(it -> !it.isAnnotationPresent(Transient.class))
                .toList();
    }
}
